package com.liuya.common.support.freemarker;

import com.liuya.common.EnumConverter;
import com.liuya.common.SpringUtils;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.utility.DeepUnwrap;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Date;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.converters.ArrayConverter;
import org.apache.commons.beanutils.converters.DateConverter;
import org.springframework.context.ApplicationContext;
import org.springframework.util.Assert;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

/**
 * Freemarker处理实用类
 * @author liuya
 * @Date   2014-7-21
 *
 */
public final class FreemarkerUtils {
	private static final ConvertUtilsBean conver = new FreemarkerUtils().new ConverUtil();
	public static final String[] DATE_PATTERNS = { "yyyy", "yyyy-MM", "yyyyMM",
		"yyyy/MM", "yyyy-MM-dd", "yyyyMMdd", "yyyy/MM/dd",
		"yyyy-MM-dd HH:mm:ss", "yyyyMMddHHmmss", "yyyy/MM/dd HH:mm:ss" };
	static {
		DateConverter dateC = new DateConverter();
		dateC.setPatterns(DATE_PATTERNS);
		conver.register(dateC, Date.class);
	}

	/**
	 * 处理模板内容，将模板中的变量使用给定的model值来替换
	 * @param templatePath
	 * @param model
	 * @return
	 */
	public static String process(String templatePath, Map<String, ?> model) {
		Configuration config = null;
		ApplicationContext appcontext = SpringUtils.getApplicationContext();
		if (appcontext != null) {
			FreeMarkerConfigurer fconf = (FreeMarkerConfigurer) SpringUtils.getBean("freeMarkerConfigurer", FreeMarkerConfigurer.class);
			if (fconf != null)
				config = fconf.getConfiguration();
		}
		return process(templatePath, model, config);
	}

	/**
	 * 处理模板内容，将模板中的变量使用给定的model值来替换
	 * @param templatePath
	 * @param model
	 * @param configuration
	 * @return
	 */
	public static String process(String templatePath, Map<String, ?> model, Configuration configuration) {
		if (templatePath == null)
			return null;
		if (configuration == null)
			configuration = new Configuration();
		StringWriter writer = new StringWriter();
		try {
			new Template("template", new StringReader(templatePath), configuration).process(model, writer);
		} catch (TemplateException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return writer.toString();
	}

	/**
	 * 返回模板参数
	 * @param name
	 *  参数名
	 * @param type
	 *   class类型
	 * @param params
	 * @return
	 * @throws TemplateModelException
	 */
	@SuppressWarnings("unchecked")
	public static <T> T getParameter(String name, Class<T> type,
			Map<String, TemplateModel> params) throws TemplateModelException {
		Assert.hasText(name);
		Assert.notNull(type);
		Assert.notNull(params);
		TemplateModel tmp = (TemplateModel) params.get(name);
		if (tmp == null)
			return null;
		Object war = DeepUnwrap.unwrap(tmp);
		return (T) conver.convert(war, type);
	}

	/**
	 * 获取变量的值
	 * @param name
	 * @param env
	 * @return
	 * @throws TemplateModelException
	 */
	public static TemplateModel getVariable(String name, Environment env)
			throws TemplateModelException {
		Assert.hasText(name);
		Assert.notNull(env);
		return env.getVariable(name);
	}

	/**
	 * 设置变量
	 * @param name
	 * @param value
	 * @param env
	 * @throws TemplateModelException
	 */
	public static void setVariable(String name, Object value, Environment env)
			throws TemplateModelException {
		Assert.hasText(name);
		Assert.notNull(env);
		if (value instanceof TemplateModel)
			env.setVariable(name, (TemplateModel) value);
		else
			env.setVariable(name, ObjectWrapper.BEANS_WRAPPER.wrap(value));
	}

	/**
	 * 批量设置变量
	 * @param variables
	 * @param env
	 * @throws TemplateModelException
	 */
	public static void setVariables(Map<String, Object> variables, Environment env) throws TemplateModelException {
		Assert.notNull(variables);
		Assert.notNull(env);
		Iterator<?> iter = variables.entrySet().iterator();
		while (iter.hasNext()) {
			@SuppressWarnings("rawtypes")
			Map.Entry entry = (Map.Entry) iter.next();
			String str = (String) entry.getKey();
			Object model = entry.getValue();
			if (model instanceof TemplateModel)
				env.setVariable(str, (TemplateModel) model);
			else
				env.setVariable(str, ObjectWrapper.BEANS_WRAPPER.wrap(model));
		}
	}

	class ConverUtil extends ConvertUtilsBean {
		
		public String convert(Object value) {
			if (value != null) {
				Class<? extends Object> clazz = value.getClass();
				if (clazz.isEnum() && super.lookup(clazz) == null) {
					super.register(new EnumConverter(clazz), clazz);
				} else if (clazz.isArray() && clazz.getComponentType().isEnum()) {
					if (super.lookup(clazz) == null) {
						ArrayConverter aco = new ArrayConverter(clazz, new EnumConverter(clazz.getComponentType()), 0);
						aco.setOnlyFirstToString(false);
						super.register((Converter) aco, clazz);
					}
					Converter localObject = super.lookup(clazz);
					return (String) localObject.convert(String.class, value);
				}
			}
			return (String) super.convert(value);
		}

		public Object convert(String value, Class<?> clazz) {
			if (clazz.isEnum() && super.lookup(clazz) == null)
				super.register(new EnumConverter(clazz), clazz);
			return super.convert(value, clazz);
		}

		public Object convert(String[] values, Class<?> clazz) {
			if (clazz.isArray() && clazz.getComponentType().isEnum() && super.lookup(clazz.getComponentType()) == null)
				super.register(new EnumConverter(clazz.getComponentType()), clazz.getComponentType());
			return super.convert(values, clazz);
		}

		public Object convert(Object value, Class<?> targetType) {
			if (super.lookup(targetType) == null)
				if (targetType.isEnum()) {
					super.register(new EnumConverter(targetType), targetType);
				} else if (targetType.isArray() && targetType.getComponentType().isEnum()) {
					ArrayConverter arrcon = new ArrayConverter(targetType, new EnumConverter(targetType.getComponentType()), 0);
					arrcon.setOnlyFirstToString(false);
					super.register(arrcon, targetType);
				}
			return super.convert(value, targetType);
		}
	}
}
